home *** CD-ROM | disk | FTP | other *** search
- """CVS locking algorithm.
-
- CVS locking strategy
- ====================
-
- As reverse engineered from the CVS 1.3 sources (file lock.c):
-
- - Locking is done on a per repository basis (but a process can hold
- write locks for multiple directories); all lock files are placed in
- the repository and have names beginning with "#cvs.".
-
- - Before even attempting to lock, a file "#cvs.tfl.<pid>" is created
- (and removed again), to test that we can write the repository. [The
- algorithm can still be fooled (1) if the repository's mode is changed
- while attempting to lock; (2) if this file exists and is writable but
- the directory is not.]
-
- - While creating the actual read/write lock files (which may exist for
- a long time), a "meta-lock" is held. The meta-lock is a directory
- named "#cvs.lock" in the repository. The meta-lock is also held while
- a write lock is held.
-
- - To set a read lock:
-
- - acquire the meta-lock
- - create the file "#cvs.rfl.<pid>"
- - release the meta-lock
-
- - To set a write lock:
-
- - acquire the meta-lock
- - check that there are no files called "#cvs.rfl.*"
- - if there are, release the meta-lock, sleep, try again
- - create the file "#cvs.wfl.<pid>"
-
- - To release a write lock:
-
- - remove the file "#cvs.wfl.<pid>"
- - rmdir the meta-lock
-
- - To release a read lock:
-
- - remove the file "#cvs.rfl.<pid>"
-
-
- Additional notes
- ----------------
-
- - A process should read-lock at most one repository at a time.
-
- - A process may write-lock as many repositories as it wishes (to avoid
- deadlocks, I presume it should always lock them top-down in the
- directory hierarchy).
-
- - A process should make sure it removes all its lock files and
- directories when it crashes.
-
- - Limitation: one user id should not be committing files into the same
- repository at the same time.
-
-
- Turn this into Python code
- --------------------------
-
- rl = ReadLock(repository, waittime)
-
- wl = WriteLock(repository, waittime)
-
- list = MultipleWriteLock([repository1, repository2, ...], waittime)
-
- """
-
-
- import os
- import time
- import stat
- import pwd
-
-
- # Default wait time
- DELAY = 10
-
-
- # XXX This should be the same on all Unix versions
- EEXIST = 17
-
-
- # Files used for locking (must match cvs.h in the CVS sources)
- CVSLCK = "#cvs.lck"
- CVSRFL = "#cvs.rfl."
- CVSWFL = "#cvs.wfl."
-
-
- class Error:
-
- def __init__(self, msg):
- self.msg = msg
-
- def __repr__(self):
- return repr(self.msg)
-
- def __str__(self):
- return str(self.msg)
-
-
- class Locked(Error):
- pass
-
-
- class Lock:
-
- def __init__(self, repository = ".", delay = DELAY):
- self.repository = repository
- self.delay = delay
- self.lockdir = None
- self.lockfile = None
- pid = `os.getpid()`
- self.cvslck = self.join(CVSLCK)
- self.cvsrfl = self.join(CVSRFL + pid)
- self.cvswfl = self.join(CVSWFL + pid)
-
- def __del__(self):
- print "__del__"
- self.unlock()
-
- def setlockdir(self):
- while 1:
- try:
- self.lockdir = self.cvslck
- os.mkdir(self.cvslck, 0777)
- return
- except os.error, msg:
- self.lockdir = None
- if msg[0] == EEXIST:
- try:
- st = os.stat(self.cvslck)
- except os.error:
- continue
- self.sleep(st)
- continue
- raise Error("failed to lock %s: %s" % (
- self.repository, msg))
-
- def unlock(self):
- self.unlockfile()
- self.unlockdir()
-
- def unlockfile(self):
- if self.lockfile:
- print "unlink", self.lockfile
- try:
- os.unlink(self.lockfile)
- except os.error:
- pass
- self.lockfile = None
-
- def unlockdir(self):
- if self.lockdir:
- print "rmdir", self.lockdir
- try:
- os.rmdir(self.lockdir)
- except os.error:
- pass
- self.lockdir = None
-
- def sleep(self, st):
- sleep(st, self.repository, self.delay)
-
- def join(self, name):
- return os.path.join(self.repository, name)
-
-
- def sleep(st, repository, delay):
- if delay <= 0:
- raise Locked(st)
- uid = st[stat.ST_UID]
- try:
- pwent = pwd.getpwuid(uid)
- user = pwent[0]
- except KeyError:
- user = "uid %d" % uid
- print "[%s]" % time.ctime(time.time())[11:19],
- print "Waiting for %s's lock in" % user, repository
- time.sleep(delay)
-
-
- class ReadLock(Lock):
-
- def __init__(self, repository, delay = DELAY):
- Lock.__init__(self, repository, delay)
- ok = 0
- try:
- self.setlockdir()
- self.lockfile = self.cvsrfl
- fp = open(self.lockfile, 'w')
- fp.close()
- ok = 1
- finally:
- if not ok:
- self.unlockfile()
- self.unlockdir()
-
-
- class WriteLock(Lock):
-
- def __init__(self, repository, delay = DELAY):
- Lock.__init__(self, repository, delay)
- self.setlockdir()
- while 1:
- uid = self.readers_exist()
- if not uid:
- break
- self.unlockdir()
- self.sleep(uid)
- self.lockfile = self.cvswfl
- fp = open(self.lockfile, 'w')
- fp.close()
-
- def readers_exist(self):
- n = len(CVSRFL)
- for name in os.listdir(self.repository):
- if name[:n] == CVSRFL:
- try:
- st = os.stat(self.join(name))
- except os.error:
- continue
- return st
- return None
-
-
- def MultipleWriteLock(repositories, delay = DELAY):
- while 1:
- locks = []
- for r in repositories:
- try:
- locks.append(WriteLock(r, 0))
- except Locked, instance:
- del locks
- break
- else:
- break
- sleep(instance.msg, r, delay)
- return list
-
-
- def test():
- import sys
- if sys.argv[1:]:
- repository = sys.argv[1]
- else:
- repository = "."
- rl = None
- wl = None
- try:
- print "attempting write lock ..."
- wl = WriteLock(repository)
- print "got it."
- wl.unlock()
- print "attempting read lock ..."
- rl = ReadLock(repository)
- print "got it."
- rl.unlock()
- finally:
- print [1]
- sys.exc_traceback = None
- print [2]
- if rl:
- rl.unlock()
- print [3]
- if wl:
- wl.unlock()
- print [4]
- rl = None
- print [5]
- wl = None
- print [6]
-
-
- if __name__ == '__main__':
- test()
-